

lib file = <file.ssz>;
lib s = <string.ssz>;
lib m = <math.ssz>;
lib consts = <consts.ssz>;
lib sff = "sff.ssz";
lib com = "common.ssz";
lib bg = "bg.ssz";
lib cfg = "config.ssz";
lib tbl = <table.ssz>;

public ^&com.PalFX bgPalFX;
bgPalFX.new(1);

&EnvShake
{
  public int time;
  public float freq;
  public int ampl;
  public float phase;
  new()
  {
    `clear();
  }
  public void clear()
  {
    `time = 0;
    `freq = .m.PI / 3.0;
    `ampl = -4;
    `phase = 0.0/0.0;
  }
  public void setDefPhase()
  {
    if(!.m.isnan(`phase)) ret;
    `phase = (`freq >= .m.PI/2.0 ? .m.PI/2.0 : 0.0);
  }
  public void next()
  {
    if(`time <= 0) ret;
    `time--;
    `phase += `freq;
  }
  public float getOffset()
  {
    if(`time <= 0) ret 0.0;
    ret (float)`ampl * 0.5 * .m.sin(`phase);
  }
}
public &EnvShake envShake;

&Player
{
  public int startx = -70, starty = 0, facing = 1;
}

&Shadow
{
  public int intensity = 128;
  public uint color = 0x808080;
  public float yscale = 0.4;
  public int fadeend = .consts.int_t::MIN, fadebgn = .consts.int_t::MIN;
}

public &Stage
{
  /^/char def;
  /^/char spr;
  /^/char bgmusic;
  /^/char name, displayname, author, nameLow, displaynameLow, authorLow;
  /^&.sff.Sff sf;
  /%&.bg.BackGround bg;
  /%&.bg.Action actionList;
  /&.tbl.IntTable!int, &.bg.Action? actionTable;
  /%&.bg.BGCtrl bgctrlList;
  public &.bg.BGCTimeLine bgctl;
  /&.bg.BGCtrl bgcdef;
  /&.bg.BGAction bga;
  /&.Shadow sdw;
  /&.Player p1, p2;
  /float leftbound = 0.0/0.0, rightbound = 0.0/0.0;
  /int screenleft = 15, screenright = 15;
  /int zoffsetlink = -1;
  /int reflection = 0;
  /bool hires = false, resetbg = true, debugbg = false, reflect = true;
  /float localscl, xscale = 1.0, yscale = 1.0;
  type action_t = &.bg.Action;
  &.sff.AirFileMethods!self? airFileMethods;
  new()
  {
    `p2.startx *= -1;
	`p2.facing = -1;
    .com.cam.stg.clear();
    `localscl = .com.cam.stg.localscl;
  }
  ^&.bg.Action getAction(int no)
  {
    ret `actionTable.get(no);
  }
  public ^action_t newAction(int no)
  {
    `actionList.new[-1].no = no;
    ^`action_t act = `actionList[#`actionList-1..-1];
    `actionTable.set(no, act);
    ret act;
  }
  void getBG(%^&.bg.BackGround bgl=, int id)
  {
    if(id < 0) ret;
    loop{index i = 0; while; do:
      if(`bg[i].id == id) bgl .= `bg[i..i+1];
      i++;
    while i < #`bg:}
  }
  public ^/char load(^/char def)
  {:<-
    public void info(&.com.Section sc=)
    {
      ^/char data, err;
      err = sc.getText("name", data=);
      branch{
      cond #err == 0 && #data > 0:
        `name = data;
      else:
        `name = `def;
      }
      err = sc.getText("displayname", data=);
      branch{
      cond #err == 0 && #data > 0:
        `displayname = data;
      else:
        `displayname = `name;
      }
      err = sc.getText("author", data=);
      if(#err == 0 && #data > 0) `author = data;
      `nameLow = .s.toLower(`name);
      `displaynameLow = .s.toLower(`displayname);
      `authorLow = .s.toLower(`author);
    }
    public void camera(&.com.Section sc=)
    {
      ^/char data;
      if(#(data = sc.get("startx")) > 0){
        .com.cam.stg.startx = .com.atonOF!.com.cam.stg.startx.self?(data);
      }
      if(#(data = sc.get("boundleft")) > 0){
        .com.cam.stg.boundleft =
          .com.atonOF!.com.cam.stg.boundleft.self?(data);
      }
      if(#(data = sc.get("boundright")) > 0){
        .com.cam.stg.boundright =
          .com.atonOF!.com.cam.stg.boundright.self?(data);
      }
      if(#(data = sc.get("boundhigh")) > 0){
        .com.cam.stg.boundhigh =
          .com.atonOF!.com.cam.stg.boundhigh.self?(data);
      }
      if(#(data = sc.get("verticalfollow")) > 0){
        .com.cam.stg.verticalfollow = .com.atof(data);
      }
      if(#(data = sc.get("tension")) > 0){
        .com.cam.stg.tension = .com.atonOF!.com.cam.stg.tension.self?(data);
      }
      if(#(data = sc.get("floortension")) > 0){
        .com.cam.stg.floortension =
          .com.atonOF!.com.cam.stg.floortension.self?(data);
      }
      if(#(data = sc.get("overdrawlow")) > 0){
        .com.cam.stg.overdrawlow =
          .com.atonOF!.com.cam.stg.overdrawlow.self?(data);
      }
    }
    public void playerinfo(&.com.Section sc=)
    {
      ^/char data;
      if(#(data = sc.get("p1startx")) > 0){
        `p1.startx = .com.atonOF!`p1.startx.self?(data);
      }
      if(#(data = sc.get("p1starty")) > 0){
        `p1.starty = .com.atonOF!`p1.starty.self?(data);
      }
      if(#(data = sc.get("p2startx")) > 0){
        `p2.startx = .com.atonOF!`p2.startx.self?(data);
      }
      if(#(data = sc.get("p2starty")) > 0){
        `p2.starty = .com.atonOF!`p2.starty.self?(data);
      }
      if(#(data = sc.get("p1facing")) > 0){
        `p1.facing = .com.atonOF!`p1.facing.self?(data);
      }
      if(#(data = sc.get("p2facing")) > 0){
        `p2.facing = .com.atonOF!`p2.facing.self?(data);
      }
      if(#(data = sc.get("leftbound")) > 0){
        `leftbound = .com.atonOF!`leftbound.self?(data);
      }
      if(#(data = sc.get("rightbound")) > 0){
        `rightbound = .com.atonOF!`rightbound.self?(data);
      }
    }
    public void scaling(&.com.Section sc=)
    {
      ^/char data;
      if(#(data = sc.get("topscale")) > 0){
        .com.cam.stg.ztopscale = .com.atof(data);
      }
    }
    public void bound(&.com.Section sc=)
    {
      ^/char data;
      if(#(data = sc.get("screenleft")) > 0){
        `screenleft = .com.atonOF!`screenleft.self?(data);
      }
      if(#(data = sc.get("screenright")) > 0){
        `screenright = .com.atonOF!`screenright.self?(data);
      }
    }
    public void stageinfo(&.com.Section sc=)
    {
      ^/char data;
      if(#(data = sc.get("zoffset")) > 0){
        .com.cam.stg.zoffset = .com.atonOF!.com.cam.stg.zoffset.self?(data);
      }
      if(#(data = sc.get("zoffsetlink")) > 0){
        `zoffsetlink = .com.atonOF!`zoffsetlink.self?(data);
      }
      if(#(data = sc.get("hires")) > 0){
        `hires = .com.atonOF!int?(data) != 0;
      }
      if(#(data = sc.get("resetbg")) > 0){
        `resetbg = .com.atonOF!int?(data) != 0;
      }
      if(#(data = sc.get("localcoord")) > 0){
        .bg.readPair!int?(.com.cam.stg.localw=, .com.cam.stg.localh=, data);
      }
      if(#(data = sc.get("xscale")) > 0){
        `xscale = .com.atonOF!`xscale.self?(data);
      }
      if(#(data = sc.get("yscale")) > 0){
        `yscale = .com.atonOF!`yscale.self?(data);
      }
    }
    public void shadow(&.com.Section sc=)
    {
      ^/char data;
      int r = 0, g = 0, b = 0;
      if(#(data = sc.get("intensity")) > 0){
        `sdw.intensity =
          .m.max!int?(0)<, .m.min!int?(255, .com.atonOF!int?(data));
      }
      if(#(data = sc.get("color")) > 0){
        ^int c = .bg.cta!c<>.self?(data);
        r = .m.max!int?(0, .m.min!int?(255, c[0]));
        if(#c < 2) break;
        g = .m.max!int?(0, .m.min!int?(255, c[1]));
        if(#c < 3) break;
        b = .m.max!int?(0, .m.min!int?(255, c[2]));
      }
      `sdw.color = (uint)(r<<16 | g<<8 | b);
      if(#(data = sc.get("yscale")) > 0) `sdw.yscale = .com.atof(data);
      if(#(data = sc.get("reflect")) > 0){
        `reflect = .com.atonOF!int?(data) != 0;
      }
      if(#(data = sc.get("fade.range")) > 0){
        .bg.readPair!int?(`sdw.fadeend=, `sdw.fadebgn=, data);
      }
    }
    public void reflection(&.com.Section sc=)
    {
      ^/char data;
      if(#(data = sc.get("intensity")) > 0){
        `reflection =
          .m.max!int?(0)<, .m.min!int?(255)<, .com.atonOF!int?(data);
      }
    }
    public void music(&.com.Section sc=, bool unicode)
    {
      ^/char data;
      if(#(data = sc.get("bgmusic")) > 0){
        `bgmusic = .com.readFileName(data, unicode);
      }
    }
    public void bgdef(&.com.Section sc=, bool unicode)
    {
      ^/char data;
      if(#(data = sc.get("spr")) > 0) `spr = .com.readFileName(data, unicode);
      if(#(data = sc.get("debugbg")) > 0){
        `debugbg = .com.atonOF!int?(data) != 0;
      }
    }
    public void bg(&.com.Section sc=, index link=)
    {
      if(#`bg > 0 && !`bg[-1].positionlink) link = #`bg - 1;
      `bg.new[-1];
      `bg[-1].read(sc=, `bg[link..link+1]);
    }
    public void bgctrldef(&.com.Section sc=)
    {
      `bgcdef.ctrlbg.new(0);
      `bgcdef.looptime = -1;
      ^/char data;
      branch{
      cond #(data = sc.get("ctrlid")) > 0:
        {
          ^int ids = .bg.cta!ids<>.self?(data);
          if(#ids == 1 && ids[0] == -1) break, else;
          loop{index i = 0; while; do:
            loop{index j = 0; while; do:
              if(ids[j] == ids[i]) break, continue;
              j++;
            while j < i:}
            `getBG(`bgcdef.ctrlbg=, ids[i]);
          continue:
            i++;
          while i < #ids:}
        }
      else:
        loop{index i = 0; while; do:
          `bgcdef.ctrlbg.new[-1] = `bg[i..i+1];
          i++;
        while i < #`bg:}
      }
      if(#(data = sc.get("looptime")) > 0){
        `bgcdef.looptime = .com.atonOF!`bgcdef.looptime.self?(data);
      }
    }
    public void bgctrl(&.com.Section sc=)
    {
      `bgctrlList.new[-1].ctrlbg = `bgcdef.ctrlbg;
      `bgctrlList[-1].looptime = `bgcdef.looptime;
      ^/char data;
      if(#(data = sc.get("ctrlid")) > 0){
        ^int ids = .bg.cta!ids<>.self?(data);
        `bgctrlList[-1].ctrlbg.new(0);
        branch{
        cond #ids == 1 && ids[0] == -1:
          loop{index i = 0; while; do:
            `bgctrlList[-1].ctrlbg.new[-1] = `bg[i..i+1];
            i++;
          while i < #`bg:
          }
        else:
          loop{
            index i = 0;
            while;
          do:
            loop{index j = 0; while; do:
              if(ids[j] == ids[i]) break, continue;
              j++;
            while j < i:
            }
            `getBG(`bgctrlList[-1].ctrlbg=, ids[i]);
          continue:
            i++;
          while i < #ids:
          }
        }
      }
      `bgctrlList[-1].read(sc=, #`bgctrlList);
    }
    bool unicode = false;
    bool infoflg = true, cameraflg = true, playerinfoflg = true;
    bool scalingflg = true, boundflg = true, stageinfoflg = true;
    bool shadowflg = true, reflectionflg = true, musicflg = true;
    bool bgdefflg = true;
    ~$void(^^/char, index=, index=) section = [void(
      ^^/char lines, index i=, index link=)
    {
      ^/char sec = lines[i];
      ^/char secname = .com.sectionName(sec=);
      if(#secname == 0) ret;
      if(.s.equ(secname, "begin ")){
        `readAction(lines, i=);
        ret;
      }
      i++;
      &.com.Section sc.parse(lines, i=);
      branch{
      cond .s.equ(secname, "info"):
        if(infoflg) info(sc=);
        infoflg = false;
      cond .s.equ(secname, "camera"):
        if(cameraflg) camera(sc=);
        cameraflg = false;
      cond .s.equ(secname, "playerinfo"):
        if(playerinfoflg) playerinfo(sc=);
        playerinfoflg = false;
      cond .s.equ(secname, "scaling"):
        if(scalingflg) scaling(sc=);
        scalingflg = false;
      cond .s.equ(secname, "bound"):
        if(boundflg) bound(sc=);
        boundflg = false;
      cond .s.equ(secname, "stageinfo"):
        if(stageinfoflg) stageinfo(sc=);
        stageinfoflg = false;
      cond .s.equ(secname, "shadow"):
        if(shadowflg) shadow(sc=);
        shadowflg = false;
      cond .s.equ(secname, "reflection"):
        if(reflectionflg) reflection(sc=);
        reflectionflg = false;
      cond .s.equ(secname, "music"):
        if(musicflg) music(sc=, unicode);
        musicflg = false;
      cond .s.equ(secname, "bgdef"):
        if(bgdefflg) bgdef(sc=, unicode);
        bgdefflg = false;
      else:
        if(.s.equ(secname[0 .. .s.cFind(" \t", secname)], "bg")){
          bg(sc=, link=);
        }
      }
    }];
    ~$void(^^/char, index=) ctrlSec = [void(^^/char lines, index i=){
      ^/char sec = lines[i];
      ^/char secname = .com.sectionName(sec=);
      if(#secname == 0) ret;
      i++;
      &.com.Section sc;
      if(secname[-1] == ' ') secname = secname[0 .. #secname-1];
      branch{
      cond .s.equ(secname, "bgctrldef"):
        sc.parse(lines, i=);
        bgctrldef(sc=);
      cond .s.equ(secname, "bgctrl"):
        sc.parse(lines, i=);
        bgctrl(sc=);
      }
    }];
    ^/char error;
    `def = def;
    ^char buf = .com.loadText(def, unicode=);
    ^^/char lines = .com.splitLines(buf);
    .s.each!^/char?([void(^/char l=){l=.s.trim(l);}], lines);
    `clear();
    loop{
      index i = 0, link = -1;
      while;
    do:
      section(:lines, i=, link=:);
      i++;
    while i < #lines:}
    loop{index i = 0; while; do:
      ctrlSec(:lines, i=:);
      i++;
    while i < #lines:}
    if(!`reflect) `reflection = 0;
    `sf.new(1);
    if(
      #.com.loadFile(def, `spr=)=>error <,
      [^/char(^/char f){ret `sf~loadFile(f, false);}] > 0)
    {
      ret def + ":"\r\n + error;
    }
    `localscl = (float).com.GameWidth / (float).com.cam.stg.localw;
    .com.cam.stg.localscl = `localscl;
    if(!.m.isfinite(`leftbound)) `leftbound = 1000.0/`localscl;
    `leftbound *= `localscl;
    if(!.m.isfinite(`rightbound)) `rightbound = 1000.0/`localscl;
    `rightbound *= `localscl;
    loop{
      index i = 0, link = 0, zlink = -1;
      while;
    do:
      `bg[i].setup(
        `sf, (float).com.cam.stg.startx,
        [void(^&.bg.Action act=, int n){act = `getAction(n);}]);
      branch{
      cond `bg[i].positionlink && i > 0:
        `bg[i].startx += `bg[link].startx;
        `bg[i].starty += `bg[link].starty;
      else:
        link = i;
      }
      if(`zoffsetlink >= 0 && zlink < 0 && `bg[i].id == `zoffsetlink){
        zlink = i;
      }
      i++;
    while i < #`bg:
      if(zlink >= 0) .com.cam.stg.zoffset += (int)(`bg[zlink].starty*`yscale);
    }
    .com.cam.stg.drawOffsetY =
      (float).com.cam.stg.localw/(float).com.cam.stg.localh
      > (float).com.GameWidth/240.0
      ? .m.min!float?(
        (float).com.cam.stg.localh * `localscl * 0.5 * (
          ((float).com.cam.stg.localw/(float).com.cam.stg.localh)
          / ((float).com.GameWidth/240.0)
          - 1.0),
        (float).m.max!int?(0, .com.cam.stg.overdrawlow))
      : 0.0;
    ret "";
  }
  type bglsig_t = $void(&.bg.BackGround=);
  public void bgCtrl(&.bg.BGCtrl bgc=)
  {:<-
    void bgLoop(^^&.bg.BackGround bglist, ~`bglsig_t dg)
    {
      loop{index i = 0; while; do:
        dg(:bglist[i]<>=:);
        i++;
      while i < #bglist:
      }
    }
    bgc.currenttime++;
    switch(bgc.typ){
    case .bg.BgcType::Anim:
      {
        ^&.bg.Action act = `getAction(bgc.v1);
        if(#act > 0) bgLoop(bgc.ctrlbg)<, [`bglsig_t(bg=){
          bg.actionno = bgc.v1;
          bg.anim.setFrames(act~frames, act~loopstart);
          bg.anim.current = 0;
        }];
      }
    case .bg.BgcType::Visible:
      bgLoop(bgc.ctrlbg)<, [`bglsig_t(bg=){
        bg.visible = bgc.v1 != 0;
      }];
    case .bg.BgcType::Enable:
      bgLoop(bgc.ctrlbg)<, [`bglsig_t(bg=){
        bg.visible = bg.active = bgc.v1 != 0;
      }];
    case .bg.BgcType::PosSet:
      bgLoop(bgc.ctrlbg)<, [`bglsig_t(bg=){
        if(bgc.setx) bg.bga.x = bgc.x;
        if(bgc.sety) bg.bga.y = bgc.y;
      }];
      if(bgc.positionlink){
        if(bgc.setx) `bga.x = bgc.x;
        if(bgc.sety) `bga.y = bgc.y;
      }
    case .bg.BgcType::PosAdd:
      bgLoop(bgc.ctrlbg)<, [`bglsig_t(bg=){
        if(bgc.setx) bg.bga.x += bgc.x;
        if(bgc.sety) bg.bga.y += bgc.y;
      }];
      if(bgc.positionlink){
        if(bgc.setx) `bga.x += bgc.x;
        if(bgc.sety) `bga.y += bgc.y;
      }
    case .bg.BgcType::SinX:
      {
        if(bgc.v1 == 0) bgc.v2 = 0;
        float tmp = (float)bgc.v3 / 360.0;
        int sxt = (int)((tmp - (float)(int)tmp) * (float)bgc.v2);
        if(sxt < 0) sxt += #bgc.v2;
        bgLoop(bgc.ctrlbg)<, [`bglsig_t(bg=){
          bg.bga.xradius = bgc.x;
          bg.bga.sinxlooptime = bgc.v2;
          bg.bga.sinxtime = sxt;
        }];
        if(bgc.positionlink){
          `bga.xradius = bgc.x;
          `bga.sinxlooptime = bgc.v2;
          `bga.sinxtime = sxt;
        }
      }
    case .bg.BgcType::SinY:
      {
        if(bgc.v1 == 0) bgc.v2 = 0;
        float tmp = (float)bgc.v3 / 360.0;
        int syt = (int)((tmp - (float)(int)tmp) * (float)bgc.v2);
        if(syt < 0) syt += #bgc.v2;
        bgLoop(bgc.ctrlbg)<, [`bglsig_t(bg=){
          bg.bga.yradius = bgc.x;
          bg.bga.sinylooptime = bgc.v2;
          bg.bga.sinytime = syt;
        }];
        if(bgc.positionlink){
          `bga.yradius = bgc.x;
          `bga.sinylooptime = bgc.v2;
          `bga.sinytime = syt;
        }
      }
    case .bg.BgcType::VelSet:
      bgLoop(bgc.ctrlbg)<, [`bglsig_t(bg=){
        if(bgc.setx) bg.bga.vx = bgc.x;
        if(bgc.sety) bg.bga.vy = bgc.y;
      }];
      if(bgc.positionlink){
        if(bgc.setx) `bga.vx = bgc.x;
        if(bgc.sety) `bga.vy = bgc.y;
      }
    case .bg.BgcType::VelAdd:
      bgLoop(bgc.ctrlbg)<, [`bglsig_t(bg=){
        if(bgc.setx) bg.bga.vx += bgc.x;
        if(bgc.sety) bg.bga.vy += bgc.y;
      }];
      if(bgc.positionlink){
        if(bgc.setx) `bga.vx += bgc.x;
        if(bgc.sety) `bga.vy += bgc.y;
      }
    }
  }
  public void action()
  {
    `bgctl.step!`self?(`=);
    `bga.action();
    loop{
      index i = 0, link = 0, zlink = -1;
      while;
    do:
      `bg[i].bga.action();
      branch{
      cond i > 0 && `bg[i].positionlink:
        `bg[i].bga.xoffset += `bg[link].bga.sinxoffset;
        `bg[i].bga.yoffset += `bg[link].bga.sinyoffset;
      else:
        link = i;
      }
      if(`zoffsetlink >= 0 && zlink < 0 && `bg[i].id == `zoffsetlink){
        zlink = i;
      }
      if(`bg[i].active) `bg[i].anim.action();
      i++;
    while i < #`bg:
      if(zlink >= 0) `bga.yoffset += `bg[zlink].bga.yoffset;
    }
  }
  public void bgDraw(bool t, float x, float y, float scl)
  {
    float bgscl = `hires ? 0.5 : 1.0;
    float yofs = .envShake.getOffset();
    float posx = x, posy = y;
    float scl2 = `localscl * scl;
    branch{
      float bhtmp = (float).m.max!int?(0, .com.cam.stg.boundhigh);
    cond posy > bhtmp:
      yofs += (posy - bhtmp) * scl2;
      posy = bhtmp;
    cond posy < (float).com.cam.stg.boundhigh:
      yofs += (posy - (float).com.cam.stg.boundhigh) * scl2;
      posy = (float).com.cam.stg.boundhigh;
    }
    if(.com.cam.stg.verticalfollow > 0.0) branch{
    cond yofs < 0.0:
      branch{
        float temp =
          ((float).com.cam.stg.boundhigh - posy) * scl2
          + (
            scl > 1.0 ? (.com.cam.screenZoff + (float)(.com.GameHeight - 240))
            : (float).com.GameHeight)
          * (1.0/scl - 1.0);
        if(temp >= 0.0) break;
      cond yofs < temp:
        yofs -= temp;
        posy += temp / scl2;
      else:
        posy += yofs / scl2;
        yofs = 0.0;
      }
    else:
      branch{
      cond -yofs < posy * scl2:
        yofs += posy * scl2;
        posy = 0.0;
      else:
        posy += yofs / scl2;
        yofs = 0.0;
      }
    }
    if(!.com.cam.zoom){
      posx = .m.ceil(posx - 0.5);
      posy = .m.ceil(posy - 0.5);
    }
    yofs +=
      (.com.cam.stg.drawOffsetY + (float)(.com.cam.stg.localh - 240)*`localscl)
      * scl**(
        (
          (
            360.0 * (float).com.cam.stg.localw
            + 160.0 * (float).com.cam.stg.localh)
          / (float).com.cam.stg.localw + .com.cam.stg.drawOffsetY)
        / 480.0);
    loop{index i = 0; while; do:
      if(`bg[i].visible && `bg[i].toplayer == t && #`bg[i].anim.spr > 0){
        `bg[i].draw(
          posx, posy, scl, .bgPalFX, bgscl, `localscl, `xscale, `yscale, yofs);
      }
      i++;
    while i < #`bg:}
  }
  public void clear()
  {
    `spr = "";
    `bgmusic = "";
    `name = "";
    `bg.new(0);
    `actionList.new(0);
    `actionTable.clear();
    `bgctrlList.new(0);
  }
  public void reset()
  {
    `bga.clear();
    `bg:<-[void(i=){i.reset();}];
    `bgctrlList:<-[void(i=){i.currenttime = 0;}];
    `bgctl.clear();
    loop{index i = #`bgctrlList-1; while; do:
      `bgctl.add(`bgctrlList[i..i+1]);
      i--;
    while i >= 0:}
  }
}


